package cn.snmp.mutual.org.opennms.protocols.snmp;

import cn.snmp.mutual.org.opennms.protocols.snmp.asn1.AsnEncoder;
import cn.snmp.mutual.org.opennms.protocols.snmp.asn1.AsnEncodingException;
import java.util.*;
import java.io.*;
import java.net.*;

public class SnmpSession {
    public static final int ERROR_TIMEOUT = -1;
    public static final int ERROR_IOEXCEPTION = -2;
    public static final int ERROR_ENCODING = -3;
    private LinkedList m_requests;
    private SnmpPeer m_peer;
    private SnmpTimer m_timer;
    private SnmpHandler m_defHandler;
    AsnEncoder m_encoder;
    private Object m_sync;
    private boolean m_stopRun;
    private SnmpPortal m_portal;
    private boolean m_threadException;
    private Throwable m_why;

    private ByteArrayInfo encode(final SnmpPduPacket pdu) throws SnmpPduEncodingException, AsnEncodingException {
        final SnmpPeer peer = this.m_peer;
        final SnmpParameters parms = peer.getParameters();
        final int begin = 0;
        int offset = 0;
        final byte[] buf = new byte[16384];
        final SnmpInt32 version = new SnmpInt32(parms.getVersion());
        offset = version.encodeASN(buf, offset, this.m_encoder);
        SnmpOctetString community;
        if (pdu.getCommand() == 163) {
            final String wrComm = parms.getWriteCommunity();
            if (wrComm == null) {
                throw new SnmpPduEncodingException("Requested SET but there is no write community");
            }
            community = new SnmpOctetString(wrComm.getBytes());
        } else {
            community = new SnmpOctetString(parms.getReadCommunity().getBytes());
        }
        offset = community.encodeASN(buf, offset, this.m_encoder);
        final int pivot;
        offset = (pivot = pdu.encodeASN(buf, offset, this.m_encoder));
        offset = this.m_encoder.buildHeader(buf, offset, (byte) 48, pivot);
        SnmpUtil.rotate(buf, 0, pivot, offset);
        return new ByteArrayInfo(buf, offset);
    }

    private ByteArrayInfo encode(final SnmpPduTrap pdu) throws SnmpPduEncodingException, AsnEncodingException {
        final SnmpPeer peer = this.m_peer;
        final SnmpParameters parms = peer.getParameters();
        final int begin = 0;
        int offset = 0;
        final byte[] buf = new byte[16384];
        final SnmpInt32 version = new SnmpInt32(parms.getVersion());
        offset = version.encodeASN(buf, offset, this.m_encoder);
        final SnmpOctetString community = new SnmpOctetString(parms.getReadCommunity().getBytes());
        offset = community.encodeASN(buf, offset, this.m_encoder);
        final int pivot;
        offset = (pivot = pdu.encodeASN(buf, offset, this.m_encoder));
        offset = this.m_encoder.buildHeader(buf, offset, (byte) 48, pivot);
        SnmpUtil.rotate(buf, 0, pivot, offset);
        return new ByteArrayInfo(buf, offset);
    }

    void addRequest(final SnmpRequest req) {
        synchronized (this.m_requests) {
            this.m_requests.addLast(req);
        }
    }

    void removeRequest(final SnmpRequest req) {
        synchronized (this.m_requests) {
            if (this.m_requests.size() > 0) {
                final ListIterator iter = this.m_requests.listIterator(0);
                while (iter.hasNext()) {
                    final SnmpRequest cmp = (SnmpRequest) iter.next();
                    if (req.equals(cmp)) {
                        req.m_expired = true;
                        iter.remove();
                    }
                }
            }
        }
    }

    SnmpRequest findRequest(final SnmpPduPacket pdu) {
        synchronized (this.m_requests) {
            if (this.m_requests.size() > 0) {
                final ListIterator iter = this.m_requests.listIterator(0);
                while (iter.hasNext()) {
                    final SnmpRequest req = (SnmpRequest) iter.next();
                    if (!req.m_expired && req.m_pdu instanceof SnmpPduPacket && ((SnmpPduPacket) req.m_pdu).getRequestId() == pdu.getRequestId()) {
                        return req;
                    }
                }
            }
        }
        return null;
    }

    SnmpTimer getTimer() {
        return this.m_timer;
    }

    void transmit(final SnmpRequest req) throws SnmpPduEncodingException, AsnEncodingException, IOException {
        SnmpPduPacket pdu = null;
        SnmpPduTrap trap = null;
        final SnmpPeer peer = this.m_peer;
        final SnmpParameters parms = peer.getParameters();
        if (req.m_pdu instanceof SnmpPduPacket) {
            pdu = (SnmpPduPacket) req.m_pdu;
        }
        if (req.m_pdu instanceof SnmpPduTrap) {
            trap = (SnmpPduTrap) req.m_pdu;
        }
        if (pdu != null) {
            switch (pdu.getCommand()) {
                case 165:
                case 166:
                case 167:
                case 168: {
                    if (parms.getVersion() < 1) {
                        throw new SnmpPduEncodingException("Cannot send pdu, invalid SNMP version");
                    }
                    break;
                }
            }
            final ByteArrayInfo msg = this.encode(pdu);
            this.m_portal.send(this.m_peer, msg.array(), msg.size());
        } else {
            if (trap == null) {
                throw new SnmpPduEncodingException("Invalid PDU type passed to method");
            }
            final ByteArrayInfo msg = this.encode(trap);
            this.m_portal.send(this.m_peer, msg.array(), msg.size());
        }
    }

    public SnmpSession(final InetAddress peer) throws SocketException {
        this.m_sync = new Object();
        this.m_requests = new LinkedList();
        this.m_peer = new SnmpPeer(peer);
        this.m_timer = new SnmpTimer();
        this.m_defHandler = null;
        this.m_stopRun = false;
        this.m_encoder = new SnmpParameters().getEncoder();
        this.m_portal = new SnmpPortal(new SessionHandler(), this.m_encoder, 0);
        this.m_threadException = false;
        this.m_why = null;
        this.m_timer.schedule(new CleanupRequest(), 1000L);
    }

    public SnmpSession(final SnmpPeer peer) throws SocketException {
        this.m_requests = new LinkedList();
        this.m_timer = new SnmpTimer();
        this.m_defHandler = null;
        this.m_sync = new Object();
        this.m_stopRun = false;
        this.m_encoder = peer.getParameters().getEncoder();
        this.m_portal = new SnmpPortal(new SessionHandler(), this.m_encoder, 0);
        this.m_threadException = false;
        this.m_why = null;
        this.m_peer = peer;
        this.m_timer.schedule(new CleanupRequest(), 5000L);
    }

    public SnmpSession(final InetAddress peer, final SnmpParameters params) throws SocketException {
        this(peer);
        this.m_peer.setParameters(params);
    }

    public SnmpHandler getDefaultHandler() {
        return this.m_defHandler;
    }

    public void setDefaultHandler(final SnmpHandler hdl) {
        this.m_defHandler = hdl;
    }

    public SnmpPeer getPeer() {
        return this.m_peer;
    }

    public void setPeer(final SnmpPeer peer) {
        this.m_peer = peer;
        this.setAsnEncoder(peer.getParameters().getEncoder());
    }

    public int getOutstandingCount() {
        synchronized (this.m_sync) {
            if (this.m_stopRun) {
                throw new IllegalStateException("illegal operation, the session has been closed");
            }
        }
        synchronized (this.m_requests) {
            if (this.m_requests.size() > 0) {
                final ListIterator iter = this.m_requests.listIterator();
                while (iter.hasNext()) {
                    final SnmpRequest req = (SnmpRequest) iter.next();
                    if (req.m_expired) {
                        iter.remove();
                    }
                }
            }
            return this.m_requests.size();
        }
    }

    public void cancel(final int requestId) {
        synchronized (this.m_sync) {
            if (this.m_stopRun) {
                throw new IllegalStateException("illegal operation, the session has been closed");
            }
        }
        synchronized (this.m_requests) {
            if (this.m_requests.size() > 0) {
                final ListIterator iter = this.m_requests.listIterator();
                while (iter.hasNext()) {
                    final SnmpRequest req = (SnmpRequest) iter.next();
                    if (req.m_expired || (req.m_pdu instanceof SnmpPduPacket && ((SnmpPduPacket) req.m_pdu).getRequestId() == requestId)) {
                        req.m_expired = true;
                        iter.remove();
                    }
                }
            }
        }
    }

    public int send(final SnmpPduPacket pdu, final SnmpHandler handler) {
        if (handler == null) {
            throw new SnmpHandlerNotDefinedException("No Handler Defined");
        }
        synchronized (this.m_sync) {
            if (this.m_stopRun) {
                throw new IllegalStateException("illegal operation, the session has been closed");
            }
        }
        final SnmpRequest req = new SnmpRequest(this, pdu, handler);
        if (pdu.getCommand() != 167) {
            this.addRequest(req);
        }
        req.run();
        if (req.m_expired) {
            return 0;
        }
        return ((SnmpPduPacket) req.m_pdu).getRequestId();
    }

    public int send(final SnmpPduPacket pdu) {
        if (this.m_defHandler == null) {
            throw new SnmpHandlerNotDefinedException("No Handler Defined");
        }
        return this.send(pdu, this.m_defHandler);
    }

    public int send(final SnmpPduTrap pdu, final SnmpHandler handler) {
        if (handler == null) {
            throw new SnmpHandlerNotDefinedException("No Handler Defined");
        }
        synchronized (this.m_sync) {
            if (this.m_stopRun) {
                throw new IllegalStateException("illegal operation, the session has been closed");
            }
        }
        final SnmpRequest req = new SnmpRequest(this, pdu, handler);
        req.run();
        return 0;
    }

    public int send(final SnmpPduTrap pdu) {
        if (this.m_defHandler == null) {
            throw new SnmpHandlerNotDefinedException("No Handler Defined");
        }
        return this.send(pdu, this.m_defHandler);
    }

    public boolean isClosed() {
        synchronized (this.m_sync) {
            return this.m_stopRun;
        }
    }

    public void close() {
        synchronized (this.m_sync) {
            if (this.m_stopRun) {
                throw new IllegalStateException("The session is already closed");
            }
            this.m_stopRun = true;
            this.m_timer.cancel();
            this.m_portal.close();
        }
        synchronized (this.m_requests) {
            this.m_requests.clear();
        }
    }

    public void raise() throws Throwable {
        synchronized (this.m_sync) {
            if (this.m_threadException) {
                throw this.m_why;
            }
        }
    }

    public void setAsnEncoder(final AsnEncoder encoder) {
        this.m_encoder = encoder;
        this.m_portal.setAsnEncoder(encoder);
    }

    public AsnEncoder getAsnEncoder() {
        return this.m_encoder;
    }

    public static void registerSyntaxObject(final SnmpSyntax object) {
        SnmpUtil.registerSyntax(object);
    }

    private class SessionHandler implements SnmpPacketHandler {
        public void processSnmpMessage(final InetAddress agent, final int port, final SnmpInt32 version, final SnmpOctetString community, final int pduType, final SnmpPduPacket pdu) throws SnmpPduEncodingException {
            boolean isExpired = true;
            SnmpRequest req = null;
            synchronized (SnmpSession.this.m_requests) {
                req = SnmpSession.this.findRequest(pdu);
                if (req != null) {
                    isExpired = req.m_expired;
                }
            }
            if (req != null && !isExpired) {
                int cmd = -1;
                if (req.m_pdu instanceof SnmpPduPacket) {
                    cmd = ((SnmpPduPacket) req.m_pdu).getCommand();
                }
                switch (cmd) {
                    case 163: {
                        final String tst = new String(community.getString());
                        final String wr = SnmpSession.this.m_peer.getParameters().getWriteCommunity();
                        if (!tst.equals(wr)) {
                            throw new SnmpPduEncodingException("Invalid community string");
                        }
                        break;
                    }
                    case 160:
                    case 161:
                    case 162:
                    case 165:
                    case 166:
                    case 168: {
                        final String tst = new String(community.getString());
                        final String rd = SnmpSession.this.m_peer.getParameters().getReadCommunity();
                        if (!tst.equals(rd)) {
                            throw new SnmpPduEncodingException("Invalid community string");
                        }
                        break;
                    }
                    default: {
                        throw new SnmpPduEncodingException("Invalid PDU Type for session received");
                    }
                }
                req.m_expired = true;
                req.m_handler.snmpReceivedPdu(req.m_session, ((SnmpPduRequest) pdu).getCommand(), pdu);
            }
        }

        public void processSnmpTrap(final InetAddress agent, final int port, final SnmpOctetString community, final SnmpPduTrap pdu) throws SnmpPduEncodingException {
            throw new SnmpPduEncodingException("Invalid PDU Type for session");
        }

        public void processBadDatagram(final DatagramPacket p) {
        }

        public void processException(final Exception e) {
        }
    }

    private class CleanupRequest implements Runnable {
        public void run() {
            synchronized (SnmpSession.this.m_requests) {
                if (SnmpSession.this.m_requests.size() > 0) {
                    final ListIterator iter = SnmpSession.this.m_requests.listIterator(0);
                    while (iter.hasNext()) {
                        final SnmpRequest req = (SnmpRequest) iter.next();
                        if (req.m_expired) {
                            iter.remove();
                        }
                    }
                }
            }
            if (!SnmpSession.this.m_stopRun && !SnmpSession.this.m_threadException) {
                SnmpSession.this.m_timer.schedule(this, 1000L);
            }
        }
    }

    private static class ByteArrayInfo {
        private byte[] m_buf;
        private int m_length;

        public ByteArrayInfo(final byte[] buf, final int length) {
            this.m_buf = buf;
            this.m_length = length;
        }

        public byte[] array() {
            return this.m_buf;
        }

        public int size() {
            return this.m_length;
        }
    }
}
